/*
 * contrib/ltree/_ltree_op.c
 *
 *
 * ltree[]的操作函数
 * Teodor Sigaev <teodor@stack.net>
 */
#include "postgres.h"

#include <ctype.h>

#include "ltree.h"

PG_FUNCTION_INFO_V1(_ltree_isparent);
PG_FUNCTION_INFO_V1(_ltree_r_isparent);
PG_FUNCTION_INFO_V1(_ltree_risparent);
PG_FUNCTION_INFO_V1(_ltree_r_risparent);
PG_FUNCTION_INFO_V1(_ltq_regex);
PG_FUNCTION_INFO_V1(_ltq_rregex);
PG_FUNCTION_INFO_V1(_lt_q_regex);
PG_FUNCTION_INFO_V1(_lt_q_rregex);
PG_FUNCTION_INFO_V1(_ltxtq_exec);
PG_FUNCTION_INFO_V1(_ltxtq_rexec);

PG_FUNCTION_INFO_V1(_ltree_extract_isparent);
PG_FUNCTION_INFO_V1(_ltree_extract_risparent);
PG_FUNCTION_INFO_V1(_ltq_extract_regex);
PG_FUNCTION_INFO_V1(_ltxtq_extract_exec);

PG_FUNCTION_INFO_V1(_lca);

typedef Datum (*PGCALL2) (PG_FUNCTION_ARGS);

#define NEXTVAL(x) ( (ltree*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) )

static bool fc_array_iterator(ArrayType *fc_la, PGCALL2 fc_callback, void *fc_param, ltree **fc_found)
{
	int			fc_num = ArrayGetNItems(ARR_NDIM(fc_la), ARR_DIMS(fc_la));
	ltree	   *fc_item = (ltree *) ARR_DATA_PTR(fc_la);

	if (ARR_NDIM(fc_la) > 1)
		ereport(ERROR,
				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
				 errmsg("array must be one-dimensional")));
	if (array_contains_nulls(fc_la))
		ereport(ERROR,
				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
				 errmsg("array must not contain nulls")));

	if (fc_found)
		*fc_found = NULL;
	while (fc_num > 0)
	{
		if (DatumGetBool(DirectFunctionCall2(fc_callback,
											 PointerGetDatum(fc_item), PointerGetDatum(fc_param))))
		{

			if (fc_found)
				*fc_found = fc_item;
			return true;
		}
		fc_num--;
		fc_item = NEXTVAL(fc_item);
	}

	return false;
}

Datum _ltree_isparent(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_la = PG_GETARG_ARRAYTYPE_P(0);
	ltree	   *fc_query = PG_GETARG_LTREE_P(1);
	bool		fc_res = fc_array_iterator(fc_la, ltree_isparent, (void *) fc_query, NULL);

	PG_FREE_IF_COPY(fc_la, 0);
	PG_FREE_IF_COPY(fc_query, 1);
	PG_RETURN_BOOL(fc_res);
}

Datum _ltree_r_isparent(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall2(_ltree_isparent,
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(0)
										));
}

Datum _ltree_risparent(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_la = PG_GETARG_ARRAYTYPE_P(0);
	ltree	   *fc_query = PG_GETARG_LTREE_P(1);
	bool		fc_res = fc_array_iterator(fc_la, ltree_risparent, (void *) fc_query, NULL);

	PG_FREE_IF_COPY(fc_la, 0);
	PG_FREE_IF_COPY(fc_query, 1);
	PG_RETURN_BOOL(fc_res);
}

Datum _ltree_r_risparent(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall2(_ltree_risparent,
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(0)
										));
}

Datum _ltq_regex(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_la = PG_GETARG_ARRAYTYPE_P(0);
	lquery	   *fc_query = PG_GETARG_LQUERY_P(1);
	bool		fc_res = fc_array_iterator(fc_la, ltq_regex, (void *) fc_query, NULL);

	PG_FREE_IF_COPY(fc_la, 0);
	PG_FREE_IF_COPY(fc_query, 1);
	PG_RETURN_BOOL(fc_res);
}

Datum _ltq_rregex(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall2(_ltq_regex,
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(0)
										));
}

Datum _lt_q_regex(PG_FUNCTION_ARGS)
{
	ArrayType  *fc__tree = PG_GETARG_ARRAYTYPE_P(0);
	ArrayType  *fc__query = PG_GETARG_ARRAYTYPE_P(1);
	lquery	   *fc_query = (lquery *) ARR_DATA_PTR(fc__query);
	bool		fc_res = false;
	int			fc_num = ArrayGetNItems(ARR_NDIM(fc__query), ARR_DIMS(fc__query));

	if (ARR_NDIM(fc__query) > 1)
		ereport(ERROR,
				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
				 errmsg("array must be one-dimensional")));
	if (array_contains_nulls(fc__query))
		ereport(ERROR,
				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
				 errmsg("array must not contain nulls")));

	while (fc_num > 0)
	{
		if (fc_array_iterator(fc__tree, ltq_regex, (void *) fc_query, NULL))
		{
			fc_res = true;
			break;
		}
		fc_num--;
		fc_query = (lquery *) NEXTVAL(fc_query);
	}

	PG_FREE_IF_COPY(fc__tree, 0);
	PG_FREE_IF_COPY(fc__query, 1);
	PG_RETURN_BOOL(fc_res);
}

Datum _lt_q_rregex(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall2(_lt_q_regex,
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(0)
										));
}


Datum _ltxtq_exec(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_la = PG_GETARG_ARRAYTYPE_P(0);
	ltxtquery  *fc_query = PG_GETARG_LTXTQUERY_P(1);
	bool		fc_res = fc_array_iterator(fc_la, ltxtq_exec, (void *) fc_query, NULL);

	PG_FREE_IF_COPY(fc_la, 0);
	PG_FREE_IF_COPY(fc_query, 1);
	PG_RETURN_BOOL(fc_res);
}

Datum _ltxtq_rexec(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall2(_ltxtq_exec,
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(0)
										));
}


Datum _ltree_extract_isparent(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_la = PG_GETARG_ARRAYTYPE_P(0);
	ltree	   *fc_query = PG_GETARG_LTREE_P(1);
	ltree	   *fc_found,
			   *fc_item;

	if (!fc_array_iterator(fc_la, ltree_isparent, (void *) fc_query, &fc_found))
	{
		PG_FREE_IF_COPY(fc_la, 0);
		PG_FREE_IF_COPY(fc_query, 1);
		PG_RETURN_NULL();
	}

	fc_item = (ltree *) palloc0(VARSIZE(fc_found));
	memcpy(fc_item, fc_found, VARSIZE(fc_found));

	PG_FREE_IF_COPY(fc_la, 0);
	PG_FREE_IF_COPY(fc_query, 1);
	PG_RETURN_POINTER(fc_item);
}

Datum _ltree_extract_risparent(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_la = PG_GETARG_ARRAYTYPE_P(0);
	ltree	   *fc_query = PG_GETARG_LTREE_P(1);
	ltree	   *fc_found,
			   *fc_item;

	if (!fc_array_iterator(fc_la, ltree_risparent, (void *) fc_query, &fc_found))
	{
		PG_FREE_IF_COPY(fc_la, 0);
		PG_FREE_IF_COPY(fc_query, 1);
		PG_RETURN_NULL();
	}

	fc_item = (ltree *) palloc0(VARSIZE(fc_found));
	memcpy(fc_item, fc_found, VARSIZE(fc_found));

	PG_FREE_IF_COPY(fc_la, 0);
	PG_FREE_IF_COPY(fc_query, 1);
	PG_RETURN_POINTER(fc_item);
}

Datum _ltq_extract_regex(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_la = PG_GETARG_ARRAYTYPE_P(0);
	lquery	   *fc_query = PG_GETARG_LQUERY_P(1);
	ltree	   *fc_found,
			   *fc_item;

	if (!fc_array_iterator(fc_la, ltq_regex, (void *) fc_query, &fc_found))
	{
		PG_FREE_IF_COPY(fc_la, 0);
		PG_FREE_IF_COPY(fc_query, 1);
		PG_RETURN_NULL();
	}

	fc_item = (ltree *) palloc0(VARSIZE(fc_found));
	memcpy(fc_item, fc_found, VARSIZE(fc_found));

	PG_FREE_IF_COPY(fc_la, 0);
	PG_FREE_IF_COPY(fc_query, 1);
	PG_RETURN_POINTER(fc_item);
}

Datum _ltxtq_extract_exec(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_la = PG_GETARG_ARRAYTYPE_P(0);
	ltxtquery  *fc_query = PG_GETARG_LTXTQUERY_P(1);
	ltree	   *fc_found,
			   *fc_item;

	if (!fc_array_iterator(fc_la, ltxtq_exec, (void *) fc_query, &fc_found))
	{
		PG_FREE_IF_COPY(fc_la, 0);
		PG_FREE_IF_COPY(fc_query, 1);
		PG_RETURN_NULL();
	}

	fc_item = (ltree *) palloc0(VARSIZE(fc_found));
	memcpy(fc_item, fc_found, VARSIZE(fc_found));

	PG_FREE_IF_COPY(fc_la, 0);
	PG_FREE_IF_COPY(fc_query, 1);
	PG_RETURN_POINTER(fc_item);
}

Datum _lca(PG_FUNCTION_ARGS)
{
	ArrayType  *fc_la = PG_GETARG_ARRAYTYPE_P(0);
	int			fc_num = ArrayGetNItems(ARR_NDIM(fc_la), ARR_DIMS(fc_la));
	ltree	   *fc_item = (ltree *) ARR_DATA_PTR(fc_la);
	ltree	  **fc_a,
			   *fc_res;

	if (ARR_NDIM(fc_la) > 1)
		ereport(ERROR,
				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
				 errmsg("array must be one-dimensional")));
	if (array_contains_nulls(fc_la))
		ereport(ERROR,
				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
				 errmsg("array must not contain nulls")));

	fc_a = (ltree **) palloc(sizeof(ltree *) * fc_num);
	while (fc_num > 0)
	{
		fc_num--;
		fc_a[fc_num] = fc_item;
		fc_item = NEXTVAL(fc_item);
	}
	fc_res = lca_inner(fc_a, ArrayGetNItems(ARR_NDIM(fc_la), ARR_DIMS(fc_la)));
	pfree(fc_a);

	PG_FREE_IF_COPY(fc_la, 0);

	if (fc_res)
		PG_RETURN_POINTER(fc_res);
	else
		PG_RETURN_NULL();
}
